import java.util.ArrayList;
import java.util.HashSet;
import java.util.Vector;

/**
 * Class representing the planning agent.
 *
 */
public class Agent {	
	/** The set of actions that the agent can perform */
	private HashSet<Action> actions;
	
	/** The set of resources available to the agent */
	public static Vector<Resource> resources;
	
	
	public Agent() {
		/* initiate a set of actions that the agent can perform, which will be searched by the POP algorithm */
		actions = new HashSet<Action>();
		
		actions.add(new Chop());
		actions.add(new Mix());
		actions.add(new Fry());
		actions.add(new Boil());
		actions.add(new Peel());
		actions.add(new Beat());
        actions.add(new Melt());
		
		/* initiate as set of resources available to the agent */
		resources = new Vector<Resource>();
		resources.add(Resource.STOVE_PLATE);
		resources.add(Resource.STOVE_PLATE);
		resources.add(Resource.PAN);
		resources.add(Resource.PAN);
		resources.add(Resource.PAN);
		resources.add(Resource.POT);
		resources.add(Resource.POT);
		resources.add(Resource.POT);
		resources.add(Resource.HANDS);
		resources.add(Resource.KNIFE);
		resources.add(Resource.CUTTING_BOARD);
		resources.add(Resource.BEATER);
		resources.add(Resource.BOWL);
		resources.add(Resource.PEELER);
	}
	
	/**
	 * Create a plan using the partial-order-planner (POP) algorithm
	 * @return
	 */
	public Plan plan(HashSet<Predicate> initialState, HashSet<Predicate> goalState) throws PlanningFailedException {
		Plan plan = new Plan(initialState, goalState);
	
		
		while(!plan.isSolution()) {
			
			/* select and unsolved subgoal */
			Tuple<Action, Predicate> subGoal = plan.selectSubGoal();
			Predicate p = subGoal.b();
			Action to = subGoal.a();

			/* select an action to solve that goal
			 * and revise the plan */
			
			// TODO: choose among actions already in the plan first?
			// TODO: put this in a seperate method!
			Action from = null;
			
			for(Action a : actions) {
				System.out.println("Resources: " + resources);
				System.out.println("Required: " + a.getResources());
				System.out.println("containsAll=" + resources.containsAll(a.getResources()));
				System.out.println("-----");
				if (SetHelper.contains(a.getAddList(), p) &&
						resources.containsAll(a.getResources())) {	// check if p is in a's addList
																	// and that there's resources in the kitchen to
																	// even consider this action
					
					ArrayList<Predicate> objects = p.getObjects();
					int duration = p.getDuration();
					
					/* add variables to the action 
					 * adhering to the fact that STRIPS requires
					 * any variables in the precondition to appear
					 * in the action's parameter list
					 */
					// FIXME: hardcoding, improve this!
					if (a instanceof Chop) {
						from = new Chop(objects.get(0), duration);
					} else if (a instanceof Mix) {
						from = new Mix(objects, duration);
					} else if (a instanceof Fry) {
						from = new Fry(objects.get(0), duration);
					} else if (a instanceof Boil) {
						from = new Boil(objects.get(0), duration);
					} else if (a instanceof Peel) {
						from = new Peel(objects.get(0), duration);
					}
					else if (a instanceof Beat) {
						from = new Beat(objects.get(0));
					} else if (a instanceof Melt) {
						from = new Melt(objects.get(0), duration);
					}
					
					break;			
				}										 
			}
			
			if (from == null)
				throw new PlanningFailedException("Problem unsolvable!");
			
			// add a new causal link: from -> to providing p
			CausalLink c = new CausalLink(from, to, p);
			plan.addCausalLink(c);
			
			// add a new ordering constraint: a < subGoal.a()
			OrderingConstraint o = new OrderingConstraint(from, to);
			
			// add ordering constraints putting the newly chosen action before 'finish' but after 'start'
			OrderingConstraint beforeFinish = new OrderingConstraint(from, plan.finish());
			OrderingConstraint afterStart = new OrderingConstraint(plan.start(), from);
		
			plan.addOrderingConstraint(beforeFinish);
			plan.addOrderingConstraint(afterStart);
			
			// do not add an ordering constraint if the following action is the Finish "action",
			// since it is added by the beforeFinish ordering constraint
			if (!to.equals(plan.finish()))
				plan.addOrderingConstraint(o);
			
			/* add action to the list of actions in the plan,
			 * also adding its preconditions to the list of open
			 * preconditions
			 */
			
			// first, add the action to the action list
			plan.addAction(from);
			
			// then, add its preconditions to the open precondition list
			HashSet<Tuple<Action, Predicate>> openPreconditions = plan.getOpenPreconditions();
			

			HashSet<Predicate> addListOfStartState = plan.start().getAddList();
			
			HashSet<Predicate> preconditions = from.getPreconditions();
			
			for (Predicate pr : preconditions) {
				
				// if a precondition of the action cannot be fulfilled, add
				// it to the list of open preconditions
				if (!SetHelper.contains2(addListOfStartState, pr)) {
						openPreconditions.add(new Tuple<Action, Predicate>(from, pr));
						
				} else {
					
					// else, add a causal link from start to the added action
					CausalLink ac = new CausalLink(plan.start(), from, pr);
					plan.addCausalLink(ac);
	
				}
			}
			
			// remove the preconditions fulfilled by the added action
			openPreconditions.remove(subGoal);
			
			/* resolve any threats created */
			HashSet<CausalLink> causalLinks = plan.getCausalLinks();
			
			Predicate provides;
			
			for(CausalLink cl : causalLinks) {	// TODO: no need to go through all?
				provides = cl.getProvides();

				if(from.getDeleteList().contains(provides) && !(cl.getTo().equals(from))) {
					// threat detected, since from is deleting a literal provided by the causal link cl
					
					plan.addOrderingConstraint(new OrderingConstraint(cl.getTo(), from));	// add an ordering constraint to resolve the threat
					
					if (!plan.isConsistent()) {
						throw new PlanningFailedException("Plan no longer consistent! Backtrack?"); // TODO: backtracking?
					}
				}
			}
		}
		
		/* Schedule the actions */
		Rsm.rpm(plan);
	
		return plan;
	}
}
